using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net.Sockets;
using System.Reflection;
using Misuzilla.Crigate.Configuration;
using Misuzilla.Crigate.Handler;
using Misuzilla.Net.Irc;
using Misuzilla.Crigate.Authenticate;

namespace Misuzilla.Crigate
{
    /// <summary>
    /// ユーザからの接続を管理するクラス
    /// </summary>
    public abstract class Session : MarshalByRefObject, IDisposable
    {
        public virtual String ConfigBasePath
        {
            get { return Path.Combine(Path.GetDirectoryName(GetType().Assembly.Location), "Configs"); }
        }

        #region Events
        /// <summary>
        /// セッション開始時のイベント
        /// </summary>
        public event EventHandler<SessionStartedEventArgs> SessionStarted;
        /// <summary>
        /// セッション終了時のイベント
        /// </summary>
        public event EventHandler<EventArgs> SessionEnded;
        #endregion

        // DefaultAppDomain Configuration
        internal ConfigurationManager ConfigurationManager { get; set; }
        
        private IList<IHandler> _handlers = new List<IHandler>();

        public Session()
        {
            User = new UserInfo();
            //Groups = new Groups();

            HandlerLoader = new HandlerLoader();
            HandlerTypeInfoSet = HandlerTypeLoader.LoadTypeInfo();
            HandlerLoader.ActivateHandlers(this, HandlerTypeInfoSet.AllHandlers, ActivateContexts.SessionInitialized);
        }

        ~Session()
        {
            Dispose();
        }

        #region プロパティ
        
        /// <summary>
        /// 
        /// </summary>
        public HandlerTypeInfoSet HandlerTypeInfoSet
        {
            get;
            private set;
        }
        
        /// <summary>
        /// ハンドラを読み込むローダークラスを取得します。
        /// </summary>
        public HandlerLoader HandlerLoader
        {
            get;
            private set;
        }

        /// <summary>
        /// グループの一覧をコレクションを取得します。
        /// </summary>
        public Groups Groups
        {
            get;
            protected set;
        }

        /// <summary>
        /// ユーザ認証済みかどうかを取得します。
        /// </summary>
        public Boolean IsAuthenticated
        {
            get;
            protected set;
        }

        /// <summary>
        /// セッションが開始済みかどうかを取得します。
        /// </summary>
        public Boolean IsStarted
        {
            get;
            protected set;
        }

        /// <summary>
        /// 現在のユーザを取得します。
        /// </summary>
        public UserInfo User
        {
            get;
            protected set;
        }

        /// <summary>
        /// ユーザの設定が保存されているディレクトリのパスを取得します
        /// </summary>
        public String UserConfigDirectory
        {
            get { return Path.Combine(ConfigBasePath, User.UserName); }
        }
        #endregion

        /// <summary>
        /// ユーザ認証を実行します。
        /// </summary>
        public virtual Boolean Authenticate()
        {
            return true;
        }

        /// <summary>
        /// セッションを終了します。
        /// </summary>
        public virtual void Close()
        {
            Dispose();
        }

        public override string ToString()
        {
            return String.Format("Session: User={0}", User.UserName);
        }
        
        #region イベント実行メソッド
        private Boolean DispatchReceivedIrcMessage(IrcMessage ircMessage)
        {
            Boolean result = true;

            result &= HandlerLoader.ExecuteHandlers((handler) => Invoke<IMessagePreHandler>("OnPreMessageReceived", typeof(IMessagePreHandler<>), handler, ircMessage));
            if (!result) return false;

            result &= HandlerLoader.ExecuteHandlers((handler) => Invoke<IMessageHandler>("OnMessageReceived", typeof(IMessageHandler<>), handler, ircMessage));
            if (!result) return false;

            result &= HandlerLoader.ExecuteHandlers((handler) => Invoke<IMessagePostHandler>("OnPostMessageReceived", typeof(IMessagePostHandler<>), handler, ircMessage));

            return result;
        }
        public virtual void OnMessageReceived(IrcMessage msg)
        {
            DispatchReceivedIrcMessage(msg);
        }
        public virtual Boolean OnPreMessageSend(IrcMessage msg)
        {
            return HandlerLoader.ExecuteHandlers((handler) => Invoke<IMessagePreSendHandler>("OnPreMessageSend", typeof(IMessagePreSendHandler<>), handler, msg));
        }
        public virtual Boolean OnPostMessageSend(IrcMessage msg)
        {
            return HandlerLoader.ExecuteHandlers((handler) => Invoke<IMessagePostSendHandler>("OnPostMessageSend", typeof(IMessagePostSendHandler<>), handler, msg));
        }
        public virtual void OnSessionStarted(String username)
        {
            FireEvent(SessionStarted, new SessionStartedEventArgs(username));
            IsStarted = true;
            HandlerLoader.ActivateHandlers(this, HandlerTypeInfoSet.AllHandlers, ActivateContexts.SessionStarted);
            //AppDomainCoordinator = AppDomainCoordinator.Create(this);
            //AppDomainCoordinator.Initialize();
        }
        public virtual void OnSessionEnded()
        {
            FireEvent(SessionEnded, EventArgs.Empty);
            IsStarted = false;
        }
        #endregion

        #region IRC メッセージメソッド

        /// <summary>
        /// IRCメッセージを送信します
        /// </summary>
        /// <param name="msg"></param>
        public abstract void Send(IrcMessage msg);

        /// <summary>
        /// JOIN などクライアントに返すメッセージを送信します
        /// </summary>
        /// <param name="msg"></param>
        public void SendServer(IrcMessage msg)
        {
            msg.Sender = User.ClientHost;
            Send(msg);
        }

        /// <summary>
        /// IRCサーバからのメッセージを送信します
        /// </summary>
        /// <param name="msg"></param>
        public void SendServerMessage(IrcMessage msg)
        {
            //msg.Prefix = Server.ServerName;
            Send(msg);
        }

        /// <summary>
        /// IRCサーバーからのメッセージを送信します
        /// </summary>
        /// <param name="message"></param>
        public void SendServerMessage(String message)
        {
            SendServerMessage(new NoticeMessage(User.Nick, message.Replace("\n", " ")));
        }

        /// <summary>
        /// サーバのエラーメッセージを送信します
        /// </summary>
        /// <param name="message"></param>
        public void SendServerErrorMessage(String message)
        {
            SendServerMessage("エラー: " + message);
        }

        /// <summary>
        /// サーバからクライアントにエラーリプライを返します。
        /// </summary>
        /// <param name="errorNum">エラーリプライ番号</param>
        /// <param name="commandParams">リプライコマンドパラメータ</param>
        public void SendErrorReply(ErrorReply errorNum, params String[] commandParams)
        {
            SendNumericReply((NumericReply)errorNum, commandParams);
        }

        /// <summary>
        /// サーバからクライアントにニュメリックリプライを返します。
        /// </summary>
        /// <param name="numReply">リプライ番号</param>
        /// <param name="commandParams">リプライコマンドパラメータ</param>
        public void SendNumericReply(NumericReply numReply, params String[] commandParams)
        {
            if (commandParams.Length > 14 || commandParams.Length < 0)
                throw new ArgumentOutOfRangeException("commandParams");

            NumericReplyMessage numMsg = new NumericReplyMessage(numReply);
            Int32 startPos = (String.IsNullOrEmpty(User.Nick) ? 0 : 1); // No nickname given などの一部のNumericReplyを返すときにニックネームがない
            numMsg.CommandParams[0] = User.Nick;
            for (Int32 i = 0; i < commandParams.Length; i++)
                numMsg.CommandParams[i+startPos] = commandParams[i];

            SendServerMessage(numMsg);
        }
        #endregion

        #region ヘルパーメソッド
        /// <summary>
        /// 
        /// </summary>
        private void CheckDisposed()
        {
            if (_isDisposed)
                throw new ObjectDisposedException(GetType().Name);
        }
        
        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="method"></param>
        /// <param name="genericInterfaceType"></param>
        /// <param name="target"></param>
        /// <param name="ircMessage"></param>
        /// <returns></returns>
        protected Boolean Invoke<T>(String method, Type genericInterfaceType, IHandler target, IrcMessage ircMessage) where T : class
        {
            Boolean result = true;
            Type interfaceType = typeof(T);
            Type targetType = target.GetType();

            // IMessageHandler<T>
            Type t = genericInterfaceType.MakeGenericType(ircMessage.GetType());
            if (t.IsAssignableFrom(targetType))
            {
                MethodInfo methodInfo = t.GetMethod(method);
                result = (Boolean)(methodInfo.Invoke(target, new Object[] { ircMessage }));
            }
            else if (interfaceType.IsAssignableFrom(targetType))
            {
                // IMessageHandler (ジェネリクスがない場合)
                Func<IrcMessage, Boolean> func = ((Func<IrcMessage, Boolean>)Delegate.CreateDelegate(typeof(Func<IrcMessage, Boolean>), target, method));
                result &= func(ircMessage);
            }

            return result;
        }

       
        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="TEventArgs"></typeparam>
        /// <param name="handlers"></param>
        /// <param name="e"></param>
        /// <returns>キャンセルされた場合にはfalseが返ります。</returns>
        [DebuggerStepThrough]
        private Boolean FireEvent<TEventArgs>(EventHandler<TEventArgs> handlers, TEventArgs e) where TEventArgs:EventArgs
        {
            if (handlers != null)
            {
                foreach (EventHandler<TEventArgs> eventHandler in handlers.GetInvocationList())
                {
                    try
                    {
                        eventHandler(this, e);
                    }
                    catch (Exception ex)
                    {
                        Trace.WriteLine(ex.ToString());
                    }
                }
            }
            if (e is CancelableEventArgs)
            {
                return !((e as CancelableEventArgs).Cancel);
            }
            
            return true;
        }
        #endregion

        #region IDisposable メンバ
        private Boolean _isDisposed = false;

        public virtual void Dispose()
        {
            if (!_isDisposed)
            {
                HandlerLoader.Dispose();
                
                GC.SuppressFinalize(this);
                _isDisposed = true;
            }
        }

        #endregion
    }
}
